require 'rake'
require 'pathname'
require 'fileutils'

verbose(ENV['verbose'] == '1')

PROG = ENV['prog'].nil? ? 'pintest' :  ENV['prog']
TARGET = "Bambino"

DEBUG = ENV['debug'] == '1'
NOFPU = ENV['nofpu'] == '1'

$using_cpp= false

def pop_path(path)
  Pathname(path).each_filename.to_a[1..-1]
end

def obj2src(fn, e)
  File.absolute_path(File.join(PROG, pop_path(File.dirname(fn)), File.basename(fn).ext(e)))
end

# Makefile .d file loader to be used with the import file loader.
# this emulates the -include $(DEPFILES) in a Makefile for the generated .d files
class DfileLoader
  include Rake::DSL

  SPACE_MARK = "\0"

  # Load the makefile dependencies in +fn+.
  def load(fn)
    return if ! File.exists?(fn)
    lines = File.read fn
    lines.gsub!(/\\ /, SPACE_MARK)
    lines.gsub!(/#[^\n]*\n/m, "")
    lines.gsub!(/\\\n/, ' ')
    lines.each_line do |line|
      process_line(line)
    end
  end

  private

  # Process one logical line of makefile data.
  def process_line(line)
    file_tasks, args = line.split(':', 2)
    return if args.nil?
    dependents = args.split.map { |d| respace(d) }
    file_tasks.scan(/\S+/) do |file_task|
      file_task = respace(file_task)
      file file_task => dependents
    end
  end

  def respace(str)
    str.tr SPACE_MARK, ' '
  end
end

# Install the handler
Rake.application.add_loader('d', DfileLoader.new)

ARMVERSION = ENV['ARMVERSION'].nil? ? '6.3.1' :  ENV['ARMVERSION']
#TOOLSDIR = '/home/morris/work/reprap/Smoothie/gcc-arm-none-eabi/bin'
TOOLSDIR = ENV['ARMTOOLS'].nil? ? '/usr/bin' :  ENV['ARMTOOLS']
TOOLSBIN = "#{TOOLSDIR}/arm-none-eabi-"
LIBSDIR = "#{TOOLSDIR}/../lib"
CC = "#{TOOLSBIN}gcc"
CCPP = "#{TOOLSBIN}g++"
LD = "#{TOOLSBIN}ld"
LDCPP = "#{TOOLSBIN}ld"
OBJCOPY = "#{TOOLSBIN}objcopy"
OBJDUMP = "#{TOOLSBIN}objdump"
SIZE = "#{TOOLSBIN}size"
AR = "#{TOOLSBIN}ar"

ARFLAGS = 'cr'

# include a defaults file if present
load 'rakefile.defaults' if File.exists?('rakefile.defaults')

# Set build type
#BUILDTYPE= ENV['BUILDTYPE'] || 'Checked' unless defined? BUILDTYPE
#puts "#{BUILDTYPE} build"

SRC = FileList[PROG + '/**/*.{c,cpp,s}']
#SRC.exclude(/#{excludes.join('|')}/) unless excludes.empty?
$using_cpp= SRC.find { |i| File.extname(i) == ".cpp" }.nil? ? false : true

OBJDIR = "#{PROG}_#{TARGET}"
OBJ = SRC.collect { |fn| File.join(OBJDIR, pop_path(File.dirname(fn)), File.basename(fn).ext('o')) }

# list of header dependency files generated by compiler
DEPFILES = OBJ.collect { |fn| File.join(File.dirname(fn), File.basename(fn).ext('d')) }

# create destination directories
SRC.each do |s|
  d= File.join(OBJDIR, pop_path(File.dirname(s)))
  FileUtils.mkdir_p(d) unless Dir.exists?(d)
end

include_dirs = ["./nuttx-export", "./nuttx-export/include"]
sys_include_dirs = ["./nuttx-export/include", "./nuttx-export/include/cxx", "./nuttx-export/include/libcxx", "./nuttx-export/arch/chip"]

# [Dir.glob([PROG + '/**/Inc/**', './mri/**/'])].flatten

INCLUDE = (include_dirs).collect { |d| "-I#{d}" }.join(" ") + " " + (sys_include_dirs).collect { |d| "-isystem #{d}" }.join(" ")

defines = %w(-DCLOCK_MONOTONIC -D__NuttX__ -D_DEBUG -D_LIBCPP_BUILD_STATIC -D_LIBCPP_NO_EXCEPTION)

DEFINES= defines.join(' ')

DEPFLAGS = '-MMD '
CSTD = '-std=c99'

if NOFPU
	CFLAGS = DEPFLAGS + " -Wall -Wshadow -Wundef  -mcpu=cortex-m4 -mthumb -g" + (DEBUG ? "-O0 " : "-O2 ")
else
	CFLAGS = DEPFLAGS + " -g -Wall -Wshadow -Wundef  -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 " + (DEBUG ? "-O0 " : "-O2 ")
end

CPPFLAGS = CFLAGS + ' -nostdinc++ -fno-builtin -fno-exceptions -fcheck-new -fno-rtti -std=gnu++11'

# Linker script to be used.
LSCRIPT = "./nuttx-export/build/spificonfig.ld"

LOPTIONS = ["--entry=__start", "-nostartfiles", "-nodefaultlibs", "-L./nuttx-export/libs",
            "--start-group",
            "-lnuttx", "-lapps",
            (NOFPU ? "#{LIBSDIR}/gcc/arm-none-eabi/#{ARMVERSION}/thumb/v7e-m/fpv4-sp/softfp/libgcc.a" : "#{LIBSDIR}/gcc/arm-none-eabi/#{ARMVERSION}/thumb/v7e-m/fpv4-sp/hard/libgcc.a"),
            "--end-group"]

LDFLAGS = " -Map=#{OBJDIR}/#{PROG}.map -T#{LSCRIPT} " + LOPTIONS.join(" ")

# tasks
# desc "Upload via stlink"
# task :upload do
#   sh "./flashit #{OBJDIR}/#{PROG}.bin"
# end

desc "Upload via openocd"
task :upload do
  sh "./flashit.openocd #{OBJDIR}/#{PROG}.elf"
end

desc "Upload via dfu"
task :dfu do
  sh "/usr/bin/dfu-util -v -R -d 0483:df11 -a 0 --dfuse-address 0x08000000 -D #{OBJDIR}/#{PROG}.bin"
end

# generate the header dependencies if they exist
import(*DEPFILES)

task :clean do
  FileUtils.rm_rf(OBJDIR)
end

task :default => [:build]

task :build => ["#{PROG}.bin", :size]

task :size do
  sh "#{SIZE} #{OBJDIR}/#{PROG}.elf"
end

task :disasm do
  sh "#{OBJDUMP} -d -f -M reg-names-std --demangle #{OBJDIR}/#{PROG}.elf > #{OBJDIR}/#{PROG}.disasm"
end

task :makelibs do
  sh "#{AR} #{ARFLAGS} hal.a #{LIB_OBJS}"
end

file "#{PROG}.bin" => ["#{PROG}.elf"] do
  sh "#{OBJCOPY} -O binary #{OBJDIR}/#{PROG}.elf #{OBJDIR}/#{PROG}.bin"
end

file "#{PROG}.elf" => OBJ do |t|
  puts "Linking for #{TARGET}"
  sh "#{$using_cpp ? LDCPP : LD} #{OBJ} #{LDFLAGS} -o #{OBJDIR}/#{t.name}"
end

#arm-none-eabi-objcopy -R .stack -O ihex ../LPC1768/main.elf ../LPC1768/main.hex
#arm-none-eabi-objdump -d -f -M reg-names-std --demangle ../LPC1768/main.elf >../LPC1768/main.disasm

rule '.o' => lambda{ |objfile| obj2src(objfile, 'cpp') } do |t|
  puts "Compiling #{t.source} for #{TARGET}"
  sh "#{CCPP} #{CPPFLAGS} #{INCLUDE} #{DEFINES} -c -o #{t.name} #{t.source}"
end

rule '.o' => lambda{ |objfile| obj2src(objfile, 'c') } do |t|
  puts "Compiling #{t.source} for #{TARGET}"
  sh "#{CC} #{CFLAGS} #{CSTD} #{INCLUDE} #{DEFINES} -c -o #{t.name} #{t.source}"
end

rule '.o' => lambda{ |objfile| obj2src(objfile, 's') } do |t|
  puts "Assembling #{t.source} for #{TARGET}"
  sh "#{CC} -c -mthumb -mcpu=cortex-m4 -mthumb-interwork -mlittle-endian -mfloat-abi=hard -mfpu=fpv4-sp-d16 -g -Wa,--no-warn -x assembler-with-cpp #{INCLUDE} -o #{t.name} #{t.source}"
end
